#simulation of record_manager
# storage/record_manager.py

from typing import Tuple, Dict, List
import struct

from .buffer import BufferManager
from .fsm import FreeSpaceManager
from .slotted_page import SlottedPage
from engine.catalog_manager import CatalogManager

# 定义 RID (Record ID) 为一个元组，包含页面ID和槽ID
RID = Tuple[int, int]

# --- 模拟系统目录 ---
# 在一个真正的数据库中，这些信息会存储在专门的目录表中
MOCK_CATALOG = {
    'students': {
        'schema': ['int', 'str', 'int'], # id, name, age
        # 实际应用中，这里会有一个 first_page_id，但我们让FSM来决定
    }
}
# storage/record_manager.py (续)

class RecordManager:
    """
    记录管理器，提供一个面向记录的接口，隐藏了底层页面的复杂性。
    """
    def __init__(self, buffer_manager: BufferManager, fsm: FreeSpaceManager, catalog_manager: CatalogManager | None = None):
        self.buffer_manager = buffer_manager
        self.fsm = fsm
        self.catalog_manager = catalog_manager

    def _serialize(self, record, schema: List[str]) -> bytes: # <-- 参数名改为 schema: List[str] 更清晰
        """将记录序列化为字节串，正确处理 int, str, bool 和 NULL。"""
        record_bytes = b''
        # --- ▼▼▼ 核心修改 ▼▼▼ ---
        for i, col_type in enumerate(schema):
            value = record[i]
            # col_type 现在直接就是 'int', 'boolean' 等字符串
            # --- ▲▲▲ 核心修改 ▲▲▲ ---

            # 首先，处理 NULL 值
            if value is None:
                record_bytes += b'\x01'
                continue
            else:
                record_bytes += b'\x00'

            # 其次，处理布尔值
            if col_type == 'boolean':
                record_bytes += struct.pack('?', value)

            # 然后，处理其他类型
            elif col_type == 'str': # 假设 schema 列表里用 'str'
                encoded_str = value.encode('utf-8')
                record_bytes += struct.pack('!I', len(encoded_str))
                record_bytes += encoded_str
            elif col_type == 'int':
                record_bytes += struct.pack('!i', value)
            elif col_type == 'float':  # <--- 添加对float类型的处理
                record_bytes += struct.pack('!f', value)
            elif col_type == 'double':
                record_bytes += struct.pack('!d', value)
            else:
                raise TypeError(f"不支持的序列化类型: {type(value)}")

        return record_bytes

    def _deserialize(self, data: bytes, schema: List[str]) -> tuple: # <-- 参数名改为 schema: List[str] 更清晰
        """将字节流反序列化为记录，正确处理 int, str, bool 和 NULL。"""
        record = []
        offset = 0
        # --- ▼▼▼ 核心修改 ▼▼▼ ---
        for col_type in schema:
            # col_type 现在直接就是 'int', 'boolean' 等字符串
        # --- ▲▲▲ 核心修改 ▲▲▲ ---
            # 读取 NULL 标记
            is_null_flag = data[offset:offset + 1]
            offset += 1
            if is_null_flag == b'\x01':
                record.append(None)
                continue

            # --- 核心修正：根据 schema 中的类型来反序列化 ---
            if col_type == 'boolean':
                value, = struct.unpack_from('?', data, offset)
                record.append(value)
                offset += 1
            elif col_type == 'int':
                value, = struct.unpack_from('!i', data, offset)
                record.append(value)
                offset += 4
            elif col_type == 'str':
                length, = struct.unpack_from('!I', data, offset)
                offset += 4
                value = data[offset:offset + length].decode('utf-8')
                record.append(value)
                offset += length
            elif col_type == 'float':  # <--- 添加对float类型的处理
                value, = struct.unpack_from('!f', data, offset)
                record.append(value)
                offset += 4
            elif col_type == 'double':
                value, = struct.unpack_from('!d', data, offset)
                record.append(value)
                offset += 8
            else:
                raise TypeError(f"不支持的反序列化类型: {col_type}")

        return tuple(record)

    def _get_schema_list(self, table_name: str) -> List[str]:
        if self.catalog_manager:
            ts = self.catalog_manager.get_table_schema(table_name)
            if ts:
                result=[]
                for col in ts.column_order:
                    dtype=ts.get_column_type(col).upper()
                    if dtype.startswith('BOOLEAN'):
                        mapped = 'boolean'
                    elif dtype.startswith('INT'):
                        mapped = 'int'
                    elif dtype.startswith('FLOAT'):  # <--- 添加对FLOAT类型的处理
                        mapped = 'float'
                    elif dtype.startswith('DOUBLE'):
                        mapped = 'double'
                    else:  # 所有其他类型 (VARCHAR, TEXT, etc.) 都映射为 str
                        mapped = 'str'
                    result.append(mapped)
                return result
        if table_name in MOCK_CATALOG:
            return MOCK_CATALOG[table_name]['schema']
        raise KeyError(f"Table '{table_name}' schema not found in catalog.")

    def _page_belongs_to_table(self, page_id: int, table_name: str) -> bool:
        if not self.catalog_manager:
            return False
        for tname, schema in self.catalog_manager._catalog.tables.items():
            if page_id in schema.pages:
                return tname == table_name
        return False

    def insert_record(self, table_name: str, record: tuple) -> RID:
        schema = self._get_schema_list(table_name)
        record_bytes = self._serialize(record, schema)
        required_space = len(record_bytes)

        if len(record) != len(schema):
            raise ValueError(f"Insert values count {len(record)} does not match table schema columns {len(schema)}")

        while True:
            page_id = self.fsm.get_new_page(required_space)
            # page isolation check
            if self.catalog_manager and not self._page_belongs_to_table(page_id, table_name):
                # allocate dedicated new page for this table
                new_page = self.buffer_manager.new_page()
                self.buffer_manager.unpin_page(new_page.page_id)
                page_id = new_page.page_id
            # ensure catalog lists the page
            if self.catalog_manager:
                ts = self.catalog_manager.get_table_schema(table_name)
                if ts and page_id not in ts.pages:
                    self.catalog_manager.add_page_to_table(table_name, page_id)
            # 2. get page object
            page_obj = self.buffer_manager.get_page(page_id)
            try:
                slotted_page = SlottedPage(page_obj)
                slot_id = slotted_page.insert_record(record_bytes)
                if slot_id is None:
                    self.buffer_manager.unpin_page(page_id)
                    # not enough space; mark space and loop again
                    self.fsm.update_page_space(page_id, slotted_page.get_free_space())
                    continue
                self.fsm.update_page_space(page_id, slotted_page.get_free_space())
                return (page_id, slot_id)
            finally:
                self.buffer_manager.unpin_page(page_id, is_dirty=True)

    def get_record(self, table_name: str, rid: RID) -> tuple or None:
        """
        根据 RID 获取一条记录。
        """
        page_id, slot_id = rid
        schema = self._get_schema_list(table_name)

        # 1. 获取页面
        page_obj = self.buffer_manager.get_page(page_id)
        try:
            # 2. 使用 SlottedPage 包装器读取记录字节
            slotted_page = SlottedPage(page_obj)
            record_bytes = slotted_page.get_record(slot_id)

            if record_bytes is None:
                return None
            
            # 3. 反序列化并返回
            return self._deserialize(record_bytes, schema)
        finally:
            # 4. Unpin 页面（只读，不脏）
            self.buffer_manager.unpin_page(page_id, is_dirty=False)

    def delete_record(self, table_name: str, rid: RID) -> bool:
        """
        根据 RID 删除一条记录。
        """
        page_id, slot_id = rid
        
        page_obj = self.buffer_manager.get_page(page_id)
        try:
            slotted_page = SlottedPage(page_obj)
            success = slotted_page.delete_record(slot_id)

            if success:
                # 成功删除后，更新FSM
                self.fsm.update_page_space(page_id, slotted_page.get_free_space())
            
            return success
        finally:
            # 进行了修改，所以页面是脏的
            self.buffer_manager.unpin_page(page_id, is_dirty=True)

    def scan_with_rid(self, table_name: str):
        """
        对指定表进行全表扫描。
        这是一个生成器函数，会逐条返回(RID, record)元组。
        """
        if not self.catalog_manager:
            return
        
        schema = self.catalog_manager.get_table_schema(table_name)
        schema_list = self._get_schema_list(table_name)

        if not schema:
            return

        for page_id in schema.pages:
            page_obj = self.buffer_manager.get_page(page_id)
            try:
                slotted_page = SlottedPage(page_obj)
                num_records, _ = slotted_page._get_header()
                for slot_id in range(num_records):
                    record_bytes = slotted_page.get_record(slot_id)
                    if record_bytes:
                        rid = (page_id, slot_id)
                        record = self._deserialize(record_bytes, schema_list)
                        yield rid, record
            finally:
                self.buffer_manager.unpin_page(page_id)